package memorphic.slide
{
	import flash.events.Event;
	import flash.utils.Dictionary;
	
	import memorphic.logging.LogType;
	
	import mx.binding.BindabilityInfo;
	import mx.core.EventPriority;
	import mx.events.FlexEvent;
	import mx.events.PropertyChangeEvent;
	import mx.logging.Log;
	import mx.utils.DescribeTypeCache;

	
	
	/**
	 * Used internally by Slide to add the basic Slide view functionality to the SlideView base class
	 */
	public class SlideDelegate
	{
		
		private var hostIsAdded:Boolean = false;
		private var hostIsInited:Boolean = false;
		
		private var host:IStateView;
		
		
		/**
		 * Keep track of all the views in the application
		 */
		internal static const views:Dictionary = new Dictionary(true);
	
		
		
		private var propertyChangeListeners:Object = new Object();
		
		
		
		
		public function SlideDelegate(host:IStateView)
		{
			this.host = host;
			views[host] = true;
			host.addEventListener(FlexEvent.PREINITIALIZE, onHostInit); 
			host.addEventListener(Event.ADDED_TO_STAGE, onHostAddedToStage);
			host.addEventListener(Event.REMOVED_FROM_STAGE, onHostRemovedFromStage);
		}
		


		private function onHostRemovedFromStage (e:Event):void
		{
			hostIsAdded = false;
		}
		private function onHostAddedToStage(e:Event):void
		{
			hostIsAdded = true;
			if( hostIsInited )	init();
		}
		private function onHostInit(e:FlexEvent):void
		{
			hostIsInited = true;
			if( hostIsAdded )	init();
		}

		
		private function init():void
		{
			var parentView:IStateView = host.parentView;
			var listener:ChangeListener;
			var desc:XML = DescribeTypeCache.describeType(host).typeDescription;
			var cascadeInfo:CascadeInfo = new CascadeInfo(desc);
			var cascaders:Object = cascadeInfo.getCascadingPropertyNames();
			var bindInfo:BindabilityInfo = DescribeTypeCache.describeType(host).bindabilityInfo;
			
			if(cascadeInfo.numProps < 1){
				// There should at least be the inherited stateManager property, so assume the user hasn't
				// configured the compiler properly
				Log.getLogger(LogType.CONFIG).warn("No Cascade definitions found. Check that the correct flex-config-slide.xml is being loaded in the mxmlc compiler settings.");
			}
				
		//	if(parentView){
				
				var parentBindInfo:BindabilityInfo = parentView ? DescribeTypeCache.describeType(parentView).bindabilityInfo : null;
				for each(var item:CascadeInfoItem in cascaders){
					var p:String = item.propName;
					var changeEvents:Object = bindInfo.getChangeEvents(p);
					var parentChangeEvents:Object = parentBindInfo ? parentBindInfo.getChangeEvents(p) : null;
					var eventName:String;
					var parentEventName:String;
					listener = new ChangeListener(host, this, p);
					
					propertyChangeListeners[p] = listener;
					listener.explicitlySet = false;
					
					for(eventName in changeEvents){
						// TODO? use [Cascader(nullValue="")] to make this more flexible (eg for numbers etc)
						if(host[p] == null && parentView){
							// TODO? use tag attribute to determine the type of the object to look for
							try {
								host[p] = parentView[p];
							}catch (e:ReferenceError){
								throw new Error("parent has no property '" + p + "'");
							}
						}
						host.addEventListener(eventName, listener.handler, false, EventPriority.BINDING);
//						if(item.changeHandlerName){
//							// evalChild probably should be invoked when the event is triggered - not when the listener is added. Otherwise there
//							// is a risk that objects have not been created yet. But this feature is likely going away anyway....
//							listener.addEventListener(PropertyChangeEvent.PROPERTY_CHANGE, evalChild(host,item.changeHandlerName) as Function);
//						}
					}
					if(parentChangeEvents){
						for(parentEventName in parentChangeEvents){
							parentView.addEventListener(parentEventName, listener.parentHandler, false, EventPriority.BINDING);
						}				
						if(eventName==null || parentEventName==null){
							throw new Error("no change events! (is this possible?)");
						}
					}
					
					if(p == "stateManager"){
						listener.addEventListener(PropertyChangeEvent.PROPERTY_CHANGE, onStateManagerChange);
					}
					
					// make sure that the change handlers are triggered the first time
					if(host[p]){
						listener.init();
					}
						
				}
		//	}
			
		}
		

		
		private function onStateManagerChange(e:PropertyChangeEvent):void
		{
			if(e.oldValue != null){
				(e.oldValue as StateManager).removeEventListener(StateEvent.STATE_CHANGE, onStateChanged);
			}
			if(host.stateManager){
				host.stateManager.addEventListener(StateEvent.STATE_CHANGE, onStateChanged);
				applyStateChange();
			}
		}
		
		private function onStateChanged(e:StateEvent):void
		{
			// TODO:optimise this so that not every view needs to run this code
			applyStateChange();
		}
		
		protected function applyStateChange():void
		{

			var stateView:ISlideStateClient = host.stateView ? host.stateView : host;
			if(stateView.slideStates.length == 0){
				return;
			}
			var oldState:ViewState = stateView.currentViewState;
			var oldStateName:String = oldState? oldState.name : "";
			var oldStateURI:String = oldState? oldState.uri : "";
			var newState:ViewState = findClosestState(stateView.slideStates, host.stateManager.stateURI);
			var newStateName:String = newState? newState.name : host.defaultState;
			var newStateURI:String = newState? newState.uri : "";

			if(newStateName != oldStateName){
				stateView.currentState = newStateName;
				if(host.stateManager.stateIsParentOf(newStateURI, oldStateURI)){
					if(oldState) oldState.dispatchEvent(new StateEvent(StateEvent.EXIT_TO_PARENT_STATE, newStateURI, oldStateURI));
					if(newState) newState.dispatchEvent(new StateEvent(StateEvent.ENTER_FROM_CHILD_STATE, newStateURI, oldStateURI));
				}else if(host.stateManager.stateIsParentOf(oldStateURI, newStateURI)){
					if(oldState) oldState.dispatchEvent(new StateEvent(StateEvent.EXIT_TO_CHILD_STATE, newStateURI, oldStateURI));
					if(newState) newState.dispatchEvent(new StateEvent(StateEvent.ENTER_FROM_PARENT_STATE, newStateURI, oldStateURI));
				}else{
					if(oldState) oldState.dispatchEvent(new StateEvent(StateEvent.EXIT_TO_UNRELATED_STATE, newStateURI, oldStateURI));
					if(newState) newState.dispatchEvent(new StateEvent(StateEvent.ENTER_FROM_UNRELATED_STATE, newStateURI, oldStateURI));
				}
				if(oldState) oldState.dispatchEvent(new StateEvent(StateEvent.EXIT_TO_ANY_STATE, newStateURI, oldStateURI));
				if(newState) newState.dispatchEvent(new StateEvent(StateEvent.ENTER_FROM_ANY_STATE, newStateURI, oldStateURI));
			}
		}
	
		
		
		protected function findClosestState(states:Array, uri:String):ViewState
		{
			//var states:Array = stateView.slideStates;
			var numStates:int = states.length;
			var bestMatch:ViewState = null;
			var uriParts:Array = uri.split("/");
			var candidate:ViewState;
			var candidateURI:String;
			var candidateParts:Array;
			var bestMatchHasWildcards:Boolean = false;
			var candidateHasWildcards:Boolean = false;
			
			eachCandidate: for(var i:int=0; i<numStates; i++){
				
				candidate = ViewState(states[i]);
				candidateURI = candidate.uri;
				candidateParts = candidateURI.split("/");
				
				if(!bestMatch 
					|| candidateURI.length > bestMatch.uri.length 
					// if the current best match has wildcards, test the new candidate in case it is a 
					// more exact match
					|| bestMatchHasWildcards && candidateURI.length == bestMatch.uri.length)
				{
					
					if(candidateParts.length <= uriParts.length){
						var numParts:int = candidateParts.length;
						
						eachPart: for(var j:int=0; j<numParts; j++){
							if(candidateParts[j] != uriParts[j]){
								
								if(candidateParts[j] == "*"){	
									candidateHasWildcards = true;
								}else{
									continue eachCandidate;
								}
							}
						}
						bestMatch = candidate;
						bestMatchHasWildcards = candidateHasWildcards;
						candidateHasWildcards = false;
					}
				}
				
			}
			if(bestMatch){
				return bestMatch;
			}else{
				return null;
			}
		}
		
		
		
		

	
	/*
		private function evalChild(obj:Object, address:String):Object{
			var parts:Array = address.split(".");
			var n:int = parts.length;
			for(var i:int=0; i<n; i++){
				obj = obj[parts[i]];
			}
			return obj;
		}
	*/ 
	}
	
	

	
	
}



import flash.events.Event;
import flash.events.EventDispatcher;
import mx.events.PropertyChangeEvent;
import memorphic.slide.SlideDelegate;
import memorphic.slide.IStateView;
import flash.events.IEventDispatcher;
import flash.events.EventPhase;
	
class ChangeListener extends EventDispatcher
{
	private var host:IStateView;
	private var delegate:SlideDelegate;
	
	public var propName:String;
	public var explicitlySet:Boolean = false;
	private var selfUpdate:Boolean = false;
	
	private var oldValue:Object;
	
	public function ChangeListener(host:IStateView, delegate:SlideDelegate, propName:String)
	{
		this.host = host;
		this.delegate = delegate;
		this.propName = propName;
	}
	
	
	public function init():void
	{
		selfUpdate = true;
		hostValueChanged();
		selfUpdate = false;
	}
	
	
	public function handler(e:Event):void
	{
		if(e != null && e is PropertyChangeEvent){
			if(PropertyChangeEvent(e).property != propName){
				return;
			}
		}
		hostValueChanged();
	}
	
	
	private function hostValueChanged():void
	{
		var newValue:Object = host[propName];
		if(!selfUpdate){
			selfUpdate = true;
			if(newValue == null){
				explicitlySet = false;
				if(host.parentView){
					try{
						newValue = host.parentView[propName];
					}catch(e:ReferenceError){
						newValue = null;
					}
				}
				host[propName] = newValue
			}else{
				explicitlySet = true;
			}
			selfUpdate = false;
		}
		dispatchEvent(new PropertyChangeEvent(PropertyChangeEvent.PROPERTY_CHANGE, false, false, null, propName, oldValue, newValue));
		oldValue = newValue;	
	}
	
	
	public function parentHandler(e:Event):void
	{
		if(host.parentView){
			if(!explicitlySet){
				selfUpdate = true;
				host[propName] = host.parentView[propName];
				selfUpdate = false;
			}			
		}else{
			IEventDispatcher(e.target).removeEventListener(e.type, parentHandler, true);
			IEventDispatcher(e.target).removeEventListener(e.type, parentHandler, false);
		}
	}
}